#include "mmalign.h"
#include "assert.h"
#include "bit.h"
#include "mstring.h"

mx_inline static void mmalign_mark (mx_mmalign_pt pmmalign, mx_void_pt addr)
{
    mx_usize_t bitno = (addr - pmmalign->addr) / pmmalign->bsize;
    mx_usize_t bitgr = bitno >> MX_BYTEWIDE_NUM;
    SETBIT(((mx_uint8_pt)pmmalign->iaddr)[bitgr], bitno & MX_BYTEWIDE_MASK);
}

mx_inline static void mmalign_unmark (mx_mmalign_pt pmmalign, mx_void_pt addr)
{
    mx_usize_t bitno = (addr - pmmalign->addr) / pmmalign->bsize;
    mx_usize_t bitgr = bitno >> MX_BYTEWIDE_NUM;
    CLSBIT(((mx_uint8_pt)pmmalign->iaddr)[bitgr], bitno & MX_BYTEWIDE_MASK);
}

mx_inline static mx_bool_t mmalign_isuse (mx_mmalign_pt pmmalign, mx_void_pt addr)
{
    mx_usize_t bitno = (addr - pmmalign->addr) / pmmalign->bsize;
    mx_usize_t bitgr = bitno >> MX_BYTEWIDE_NUM;
    GETBIT(((mx_uint8_pt)pmmalign->iaddr)[bitgr], bitno & MX_BYTEWIDE_MASK);
}

/**
 * memory align initialization
 * @param pmmalign: the descriptor of memory align
 * @param addr: the start address of controlled memory space
 * @param iaddr: the start address of additional information region, passing 'mx_null' means that append to the data region
 * @param bsize: block size
 * @param bcount: block count
 * @return: the size of additional information region
 */
mx_usize_t mx_mmalign_init (mx_mmalign_pt pmmalign, mx_void_pt addr, mx_void_pt iaddr, mx_usize_t bsize, mx_usize_t bcount)
{
    assert_true(pmmalign != mx_null);
    assert_true(addr != mx_null);
    assert_true(bsize != 0);
    assert_true(bcount != 0);

    pmmalign->addr = addr;
    pmmalign->bsize = bsize;
    pmmalign->end = addr + bsize * bcount;
    pmmalign->bcount = bcount;
    pmmalign->apointer = addr;
    pmmalign->fpointer = mx_null;

    if (iaddr == mx_null)
    {
        pmmalign->iaddr = pmmalign->end;
    }
    else
    {
        pmmalign->iaddr = iaddr;
    }
    /* calculate the size of additional information region */
    pmmalign->isize = (bcount + MX_BYTEWIDE_MASK) >> MX_BYTEWIDE_NUM; // cost of byte
    pmmalign->isize += MX_CPU_BITWIDE_MASK;
    pmmalign->isize &= ~MX_CPU_BITWIDE_MASK;

    return pmmalign->isize;
}

mx_void_pt mx_mmalign_alloc (mx_mmalign_pt pmmalign)
{
    mx_void_pt ret = mx_null;
    if (pmmalign->fpointer != mx_null)
    {
        /* use fpointer's block if fpointer not null */
        ret = pmmalign->fpointer;
        mmalign_mark(pmmalign, ret);
        pmmalign->fpointer = (mx_void_pt)*((mx_addr_pt)pmmalign->fpointer);
    }
    else
    {
        /* use apointer's block if apointer not out of range */
        if ((pmmalign->apointer + pmmalign->bsize) <= pmmalign->end)
        {
            ret = pmmalign->apointer;
            mmalign_mark(pmmalign, ret);
            pmmalign->apointer += pmmalign->bsize;
        }
    }
    return ret;
}

void mx_mmalign_free (mx_mmalign_pt pmmalign, mx_void_pt addr)
{
    if (addr >= pmmalign->addr && addr <= pmmalign->end)
    {
        /* block align */
        addr = (mx_void_pt)(((((mx_usize_t)addr - (mx_usize_t)pmmalign->addr) / pmmalign->bsize) * pmmalign->bsize) + pmmalign->addr);
        mmalign_unmark(pmmalign, addr);
        *((mx_addr_pt)addr) = (mx_addr_t)pmmalign->fpointer;
        pmmalign->fpointer = addr;
    }
}

void mx_mmalign_freeall (mx_mmalign_pt pmmalign)
{
    mx_memset(pmmalign->iaddr, 0, pmmalign->isize);
    pmmalign->apointer = pmmalign->addr;
    pmmalign->fpointer = mx_null;
}
